/*
 * Copyright (c) 2023 Loongson(GD) Inc.
 * Author: loongson-gz
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <errno.h>
#include "mtd_io.h"

#ifndef IF_RES_FAILURE_RETURN
#define IF_RES_FAILURE_RETURN(x) if (x) \
									return EXIT_FAILURE;
#endif

// #define PROC_MTD_PATH "/home/loongson/桌面/code-source/device_usage/test"
#define PROC_MTD_PATH "/proc/mtd"

static int mtd_num_get(char* mtd_info_line, char* mtd_name);
static int mtd_num_match_by_name(char* mtd_info_line, char* mtd_name);
static int mtd_num_probe_by_name(char* mtd_name);

static int init_mtd_info_open_fd(mtd_info* new_info);
static int init_mtd_info_ioctl(mtd_info* new_info);
static int init_mtd_info(mtd_info* new_info);
static int deinit_mtd_info(mtd_info** new_info);

static int io_mtd_size_invaild(mtd_info* info, unsigned long data_size, unsigned long offset);

int open_mtd(char* mtd_name, mtd_info** info)
{
	int mtd_num;
	int ret;
	mtd_info* new_info;
	if (!mtd_name || !info || info[0])
	{
		return EXIT_FAILURE;
	}
	mtd_num = mtd_num_probe_by_name(mtd_name);
	if (mtd_num == -1)
	{
		fprintf(stderr, "Error: %s: mtd_name not match in %s\n", __func__, PROC_MTD_PATH);
		return EXIT_FAILURE;
	}

	new_info = (mtd_info*)calloc(1, sizeof(mtd_info));
	new_info->mtd_num = mtd_num;
	ret = init_mtd_info(new_info);
	if (ret)
	{
		deinit_mtd_info(&new_info);
		new_info = NULL;
		return EXIT_FAILURE;
	}
	info[0] = new_info;
	return EXIT_SUCCESS;
}

int close_mtd(mtd_info** info)
{
	return deinit_mtd_info(info);
}

int read_mtdblock(mtd_info* info, unsigned char* data, unsigned long data_size, unsigned long offset)
{
	if (!info || !data || !data_size)
	{
		return EXIT_FAILURE;
	}

	if (io_mtd_size_invaild(info, data_size, offset))
	{
		fprintf(stderr, "Error: %s: io size invaild! %s size: %ld request: size: %ld offset: %ld\n", __func__,
				info->mtd_dev_name, info->size, data_size, offset);
		return EXIT_FAILURE;
	}

	pread(info->mtdblock_fd, data, data_size, offset);

	return EXIT_SUCCESS;
}

int write_mtdblock(mtd_info* info, unsigned char* data, unsigned long data_size, unsigned long offset)
{
	if (!info || !data || !data_size)
	{
		return EXIT_FAILURE;
	}

	if (io_mtd_size_invaild(info, data_size, offset))
	{
		fprintf(stderr, "Error: %s: io size invaild! %s size: %ld request: size: %ld offset: %ld\n", __func__,
				info->mtd_dev_name, info->size, data_size, offset);
		return EXIT_FAILURE;
	}

	pwrite(info->mtdblock_fd, data, data_size, offset);

	return EXIT_SUCCESS;
}

static int mtd_num_get(char* mtd_info_line, char* mtd_name)
{
	int index = 3; // mtdxxx:
	int num = 0;

	if (!mtd_info_line || !mtd_name)
	{
		return -1;
	}

	index = 3;
	for (; mtd_info_line[index] != ':'; ++index)
	{
		num *= 10;
		num += mtd_info_line[index] - '0';
	}
	return num;
}

static int mtd_num_match_by_name(char* mtd_info_line, char* mtd_name)
{
	int next;
	int index;
	int index_name;
	if (!mtd_info_line || !mtd_name)
	{
		return -1;
	}
	next = 0;
	for (index = 0; (mtd_info_line[index] != '\0'); ++index)
	{
		if (mtd_info_line[index] == '"')
		{
			next = 1;
			break;
		}
	}
	if (!next)
		return -1;
	++index; // 第一个冒号已经匹配
	index_name = 0;
	next = 0;
	for (; mtd_info_line[index] != '\0'; ++index, ++index_name)
	{
		if (!mtd_name[index_name])
		{
			// 名字有长度 并且 后面一个是 冒号，代表匹配
			if (index_name && (mtd_info_line[index] == '"'))
			{
				next = 1;
				break;
			}
			break;
		}
		// 不匹配 提前退出
		if (mtd_info_line[index] != mtd_name[index_name])
			break;
	}

	if (next)
	{
		return mtd_num_get(mtd_info_line, mtd_name);
	}
	return -1;
}

static int mtd_num_probe_by_name(char* mtd_name)
{
	FILE* fp;
	char* temp;
	int ret;
	if (!mtd_name)
	{
		return -1;
	}

	temp = (char*)calloc(256, sizeof(char));
	// 读取 cat /proc/mtd

	fp = fopen(PROC_MTD_PATH, "r");
	if (!fp)
	{
		return -1;
	}
	while (!feof(fp))
	{
		memset(temp, 0, 256);
		fgets(temp, 256, fp);
		ret = mtd_num_match_by_name(temp, mtd_name);
		if (ret != -1) // ok
		{
			break;
		}
	}

	free(temp);
	temp = NULL;
	return ret;
}

static int init_mtd_info_open_fd(mtd_info* new_info)
{
	new_info->mtd_dev_name = (char*)calloc(32, sizeof(char));
	sprintf(new_info->mtd_dev_name, "/dev/mtd%d", new_info->mtd_num);
	new_info->mtd_fd = open(new_info->mtd_dev_name, O_RDWR);
	if (new_info->mtd_fd < 0)
	{
		fprintf(stderr, "Error: %s: %s open failed! (%d) %d %d\n", __func__, new_info->mtd_dev_name, errno, getgid(), getuid());
		free(new_info->mtd_dev_name);
		new_info->mtd_dev_name = NULL;
		new_info->mtd_fd = 0;
		return EXIT_FAILURE;
	}

	new_info->mtdblock_dev_name = (char*)calloc(32, sizeof(char));
	sprintf(new_info->mtdblock_dev_name, "/dev/mtdblock%d", new_info->mtd_num);
	new_info->mtdblock_fd = open(new_info->mtdblock_dev_name, O_RDWR);
	if (new_info->mtdblock_fd < 0)
	{
		fprintf(stderr, "Error: %s: %s open failed!\n", __func__, new_info->mtdblock_dev_name);
		free(new_info->mtdblock_dev_name);
		new_info->mtdblock_dev_name = NULL;
		new_info->mtdblock_fd = 0;

		close(new_info->mtd_fd);
		free(new_info->mtd_dev_name);
		new_info->mtd_dev_name = NULL;
		new_info->mtd_fd = 0;
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

static int init_mtd_info_ioctl(mtd_info* new_info)
{
	int ret;
	mtd_info_t ioctl_info;
	if (!new_info)
	{
		return EXIT_FAILURE;
	}

	ret = ioctl(new_info->mtd_fd, MEMGETINFO, &ioctl_info);
	if (ret)
	{
		fprintf(stderr, "Error: %s: cant get mtd info\n", __func__);
		return EXIT_FAILURE;
	}
	new_info->size = (unsigned long)(ioctl_info.size);
	new_info->erase_size = (unsigned long)(ioctl_info.erasesize);
	new_info->write_size = ioctl_info.writesize;
	return EXIT_SUCCESS;
}

static int init_mtd_info(mtd_info* new_info)
{
	int ret;

	if (!new_info)
	{
		return EXIT_FAILURE;
	}

	// fd
	ret = init_mtd_info_open_fd(new_info);
	IF_RES_FAILURE_RETURN(ret);
	// size info
	ret = init_mtd_info_ioctl(new_info);
	IF_RES_FAILURE_RETURN(ret);

	return EXIT_SUCCESS;
}

static int deinit_mtd_info(mtd_info** new_info)
{
	if (!new_info || !new_info[0])
	{
		return EXIT_FAILURE;
	}

	if (new_info[0]->mtd_dev_name)
	{
		free(new_info[0]->mtd_dev_name);
		new_info[0]->mtd_dev_name = NULL;
	}
	if (new_info[0]->mtd_fd > 0)
	{
		close(new_info[0]->mtd_fd);
	}
	if (new_info[0]->mtdblock_dev_name)
	{
		free(new_info[0]->mtdblock_dev_name);
		new_info[0]->mtdblock_dev_name = NULL;
	}
	if (new_info[0]->mtdblock_fd > 0)
	{
		close(new_info[0]->mtdblock_fd);
	}
	free(new_info[0]);
	new_info[0] = NULL;
	return EXIT_SUCCESS;
}

static int io_mtd_size_invaild(mtd_info* info, unsigned long data_size, unsigned long offset)
{
	int io_max_byte = data_size + offset;
	return (info->size >= io_max_byte) ? 0 : 1;
}